#pragma once
#include "test_runner.h"
#include "test_report.h"
#include "test_exceptions.h"
#include "test_asserts.h"
#include "test_result.h"

#include <string>
#include <map>

#define _default_size normal

// shorthand helper macro
#define ttv template<> template<> void

// another helper macro that defines the simplest test possible:
//  - empty fixture
//  - typedefs test suite to suite_t
//  - typedefs test case to case_t
//  - supports up to default amount of test cases
//  - register test suite using the parameter name
#define simple_test_case(name) \
	class empty_fixture {}; \
	fixture_test_case(name, empty_fixture);

// another helper macro that does almost the same as simple_test_case but
// it allows the specification of a fixture class.
#define fixture_test_case(name, fixture) \
	fixture_test_case_size(name, fixture, _default_size); 

// specify the number of test cases
#define simple_test_case_size(name, size) \
	fixture_test_case_size(name, empty_fixture, size);

// same as fixture_test_case but also specifies the number of test cases
#define fixture_test_case_size(name, fixture, size) \
	typedef krryn::test::test_suite<fixture, krryn::test::##size> suite_t; \
	typedef suite_t::test_case_t case_t; \
	suite_t g_SimpleTest(name);

namespace krryn{
namespace test{
	/**
	 * optional fixture base class.
	 * notice that set_up and tear_down will get called 
	 * test_suite::m_MaxTests amount of times, no matter what.
	 */
	class fixture{
	public:
		// run before each call to test<...>
		virtual void set_up() {}

		// run after each call to test<...>
		virtual void tear_down() {}
	};

	/**
	 * deriving from a template argument has two benefits:
	 *  - it makes all the fixture's method available to test_case
	 *  - it creates a destinct type that helps us create different test_suites
	 */
	template<typename _Fixture>
	class test_case : public _Fixture{
	public:
		test_case() : m_Name("") {}
		virtual ~test_case() {}

		template<size_t _Nr>
		void test(){
			throw ignore();
		}

		// run the specified test case, with fixtures (if enabled)
		template<size_t _Nr>
		void run(){
			// notice: dynamic_cast needs a polymorphic type,
			// that is a class with at least one virtual function.
			// in this case that is the virtual destructor.
			if(fixture *l_Fixture = dynamic_cast<fixture*>(this)){
				l_Fixture->set_up();
				test<_Nr>();
				l_Fixture->tear_down();
			}else{
				test<_Nr>();
			}
		}

		std::string get_name() const{ return m_Name; }
		void set_name(const std::string &a_Name){ m_Name = a_Name; }
		void n(const std::string &a_Name){ set_name(a_Name); }
		void reset_name() { set_name(""); }
	private:
		std::string m_Name;
	};

	// Use template recursion to bind the tests to the group
	template<typename _Case, typename _Suite, size_t _Nr>
	class bind_tests{
	public:
		// need static method because partial template specialization
		// doesn't exist for normal functions.
		static void to(_Suite &a_Suite){
			a_Suite.bind(&_Case::template run<_Nr>, _Nr);
			bind_tests<_Case, _Suite, _Nr - 1>::to(a_Suite);
		}
	};

	// End template recursion
	template<typename _Case, typename _Suite>
	class bind_tests<_Case, _Suite, 0>{
	public:
		static void to(_Suite &a_Group) {}
	};

	/**
	 * base class for the test suite, only here because we need to call run_all
	 * polymorphically, which can't be done on test_suite directly due to the 
	 * fact that it takes template parameters.
	 */
	class basic_test_suite{
	public:
		virtual std::string get_name() const { return "anonymous"; }
		virtual void run_all(basic_report &a_Report) const {}
		virtual size_t max_tests() const = 0;
	};

	/**
	 * Various levels of recursion used by the test suite.
	 * These are here primarily for:
	 *  - optimization (test compilation is slow)
	 *  - large test suites
	 */
	enum test_suite_size{
		small       = 5,
		normal      = 50, // default
		large       = 150,
		extra_large = 300,
		huge        = 500
	};

	template<typename _Fixture, size_t _MaxTests = _default_size>
	class test_suite : basic_test_suite{
	public:
		// The maximum number of tests in this test suite
		enum{ m_MaxTests = _MaxTests };

		typedef test_case<_Fixture> test_case_t;
		typedef void (test_case_t::*test_method_t)();

		test_suite(const std::string &a_Name) : m_Name(a_Name){
			// attach this suite to be executed
			runner->add_suite(*this, a_Name);

			// find test cases in this suite
			bind_tests<test_case<_Fixture>, test_suite, _MaxTests>::to(*this);
		}

		virtual size_t max_tests() const{
			return m_MaxTests;
		}

		virtual std::string get_name() const{ return m_Name; }

		void bind(test_method_t a_Test, size_t a_Nr){
			m_TestCases[a_Nr] = a_Test;
		}

		virtual void run_all(basic_report &a_Report) const{
			test_case_t l_TestCase;

			a_Report.suite_begin(m_Name);

			// iterate over all the tests while calling each test-cases
			// function pointer
			for(test_list_t::const_iterator i = m_TestCases.begin(); i != m_TestCases.end(); i++){
				a_Report.test_begin(i->first);

				result l_Result = run_test(l_TestCase, i->first, i->second);

				l_TestCase.reset_name();

				a_Report.test_end(i->first, l_Result);
			}

			a_Report.suite_end(m_Name, m_MaxTests);
		}
	private:
		result run_test(test_case_t &a_TestCase, size_t a_Nr, test_method_t a_Method) const{
			try{
				(a_TestCase.*a_Method)();
			}catch(ignore &){
				// ignore, this is an empty test
				return result(m_Name, a_Nr, a_TestCase.get_name(), result::ignore);
			}catch(test_exception &a_TestException){
				return a_TestException.to_result(m_Name, a_Nr, a_TestCase.get_name());
			}

			return result(m_Name, a_Nr, a_TestCase.get_name(), result::ok);
		}

		typedef std::map<size_t, test_method_t> test_list_t;
		test_list_t m_TestCases;
		std::string m_Name;
	};
}
}